﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using X3DXNA.services;
using X3DXNA.interfaces;
using X3DXNA.core.Boundings;
using X3DXNA.core.CustomNodes;

namespace X3DXNA.basicModels
{
    class basicSphere : IGeometry
    {
        List<VertexPositionNormal> vertices;
        List<Int16> indices;

        VertexPositionNormal[] verticesArray;
        Int16[] indicesArray;

        VertexBuffer vertexBuffer;
        IndexBuffer indexBuffer;
        myBasicEffect basicEffect;
        MyBoundingSphere boundingSphere;

        Vector3 pos, size;
        Matrix world = Matrix.Identity, rotation;
        Game game;
        public String DEF { get; set; }
        public Matrix World { get { return world; } set { world = value; } }
        public Matrix getBasicTransformation { get { return Matrix.Identity; } }

        public void applyWorldTransformation(Matrix transformation)
        {
            world *= transformation;
        }

        public void applyRotation(Matrix value)
        {
            rotation = value;
            recalculateWorld();
        }

        private void recalculateWorld()
        {
            world = Matrix.Identity * rotation * Matrix.CreateTranslation(pos) * Matrix.CreateScale(size);
        }

        public basicSphere(Game game, String DEF, float radius, Matrix transformation)
        {
            this.DEF = DEF;
            this.game = game;
            this.world = transformation;

            if (radius == 0)
                radius = 1;

            vertices = new List<VertexPositionNormal>();
            indices = new List<Int16>();

            this.basicEffect = (myBasicEffect)game.Services.GetService(typeof(myBasicEffect));

            Int16 tessellation = 25;

            Int16 verticalSegments = tessellation;
            Int16 horizontalSegments = (Int16)(tessellation * 2);

            // Start with a single vertex at the bottom of the sphere.
            vertices.Add(new VertexPositionNormal(Vector3.Down * radius, Vector3.Down));

            // Create rings of vertices at progressively higher latitudes.
            for (int i = 0; i < verticalSegments - 1; i++)
            {
                float latitude = ((i + 1) * MathHelper.Pi /
                                            verticalSegments) - MathHelper.PiOver2;

                float dy = (float)Math.Sin(latitude);
                float dxz = (float)Math.Cos(latitude);

                // Create a single ring of vertices at this latitude.
                for (int j = 0; j < horizontalSegments; j++)
                {
                    float longitude = j * MathHelper.TwoPi / horizontalSegments;

                    float dx = (float)Math.Cos(longitude) * dxz;
                    float dz = (float)Math.Sin(longitude) * dxz;

                    Vector3 normal = new Vector3(dx, dy, dz);

                    vertices.Add(new VertexPositionNormal(normal * radius, normal));
                }
            }

            // Finish with a single vertex at the top of the sphere.
            vertices.Add(new VertexPositionNormal(Vector3.Up * radius, Vector3.Up));

            // Create a fan connecting the bottom vertex to the bottom latitude ring.
            for (Int16 i = 0; i < horizontalSegments; i++)
            {
                indices.Add(0);
                indices.Add((Int16)(1 + (i + 1) % horizontalSegments));
                indices.Add((Int16)(1 + i));
            }

            // Fill the sphere body with triangles joining each pair of latitude rings.
            for (int i = 0; i < verticalSegments - 2; i++)
            {
                for (int j = 0; j < horizontalSegments; j++)
                {
                    int nextI = i + 1;
                    int nextJ = (j + 1) % horizontalSegments;

                    indices.Add((Int16)(1 + i * horizontalSegments + j));
                    indices.Add((Int16)(1 + i * horizontalSegments + nextJ));
                    indices.Add((Int16)(1 + nextI * horizontalSegments + j));

                    indices.Add((Int16)(1 + i * horizontalSegments + nextJ));
                    indices.Add((Int16)(1 + nextI * horizontalSegments + nextJ));
                    indices.Add((Int16)(1 + nextI * horizontalSegments + j));
                }
            }

            // Create a fan connecting the top vertex to the top latitude ring.
            for (int i = 0; i < horizontalSegments; i++)
            {
                indices.Add((Int16)(vertices.Count - 1));
                indices.Add((Int16)(vertices.Count - 2 - (i + 1) % horizontalSegments));
                indices.Add((Int16)(vertices.Count - 2 - i));
            }

            verticesArray = vertices.ToArray();
            indicesArray = indices.ToArray();

            //VertexPositionNormal.transform(verticesArray, transformation, verticesArray);

            Vector3[] points = new Vector3[verticesArray.Length];
            for (int i = 0; i < verticesArray.Length; i++)
            {
                points[i] = verticesArray[i].Position;
            }

            boundingSphere = new MyBoundingSphere(BoundingSphere.CreateFromPoints(points));

            initializeVertexBuffer();
        }

        public void initializeVertexBuffer()
        {
            vertexBuffer = new VertexBuffer(
                game.GraphicsDevice, 
                typeof(VertexPositionNormal), 
                verticesArray.Length, 
                BufferUsage.WriteOnly);

            indexBuffer = new IndexBuffer(game.GraphicsDevice, typeof(Int16), indicesArray.Length, BufferUsage.WriteOnly);

            vertexBuffer.SetData<VertexPositionNormal>(verticesArray, 0, verticesArray.Length);
            indexBuffer.SetData<Int16>(indicesArray);
        }

        public void draw()
        {
            game.GraphicsDevice.SetVertexBuffer(vertexBuffer);
            game.GraphicsDevice.Indices = indexBuffer;

            foreach (EffectPass pass in basicEffect.getBasicEffect.CurrentTechnique.Passes)
            {
                pass.Apply();

                game.GraphicsDevice.DrawIndexedPrimitives(
                    PrimitiveType.TriangleList,
                    0,
                    0,
                    verticesArray.Length,
                    0,
                    indicesArray.Length / 3
                );
            }
        }

        public IBounding retrieveBounding()
        {
            return boundingSphere;
        }

        public Vector3[] getTriangleVertices()
        {
            return null;
        }
    }
}
